package com.example.wac.service.impl;

import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.wac.constant.Constant;
import com.example.wac.entity.Goods;
import com.example.wac.enums.ErrorCode;
import com.example.wac.mapper.GoodsMapper;
import com.example.wac.service.GoodsService;
import com.example.wac.vo.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 商品表 服务实现类
 * </p>
 *
 * @author wac
 * @since 2022-01-12
 */
@Slf4j
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements GoodsService {

    @Autowired
    RedisTemplate redisTemplate;

    private ReentrantLock lock = new ReentrantLock();

    @Override
    public ApiResult distributedLock(Integer goodsId) {
        Long userId = 1L;
        Goods goods = new LambdaQueryChainWrapper<>(this.baseMapper)
                .eq(Goods::getId, goodsId)
                .one();
        String key = Constant.Lock.DISTRIBUTE_LOCK + ":" + goods.getGoodsName();
        // 加过期时间避免秒杀商品太久其他用户一直等待
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, userId, 3, TimeUnit.SECONDS);
        if (!ifAbsent){
            throw ErrorCode.TOO_MANY_CURRENT_REQUESTS.getAppException();
        }
        try {
            if (goods.getGoodsCount() < 1){
                throw ErrorCode.STOCK_NOT_ENOUGH.getAppException();
            }
            new LambdaUpdateChainWrapper<>(this.baseMapper)
                    .eq(Goods::getId, goodsId)
                    .set(Goods::getGoodsCount,goods.getGoodsCount() - 1)
                    .update();
        }finally {
            // 1.需要做判断，只允许当前线程删除自己的key 2.删除key避免出现异常分布式锁无法释放
            if (Objects.equals(userId, redisTemplate.opsForValue().get(key))){
                redisTemplate.delete(key);
            }
        }
        log.info("当前["+ goods.getGoodsName() +"]剩余(台):"+ (goods.getGoodsCount() - 1));
        return ApiResult.success("当前["+ goods.getGoodsName() +"]剩余(台):"+ (goods.getGoodsCount() - 1));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult singleLock(Integer goodsId) {
        // 添加transactionId 对应logback-spring.xml [%X{transactionId}]
        MDC.put("transactionId", String.valueOf(UUID.randomUUID()));
        Goods goods = new LambdaQueryChainWrapper<>(this.baseMapper)
                .eq(Goods::getId, goodsId)
                .one();
       try {
           boolean flag = lock.tryLock();
           // 获取锁失败
           if (!flag){
               throw ErrorCode.TOO_MANY_CURRENT_REQUESTS.getAppException();
           }
           // 库存不足
           if (goods.getGoodsCount() < 1){
               throw ErrorCode.STOCK_NOT_ENOUGH.getAppException();
           }
           new LambdaUpdateChainWrapper<>(this.baseMapper)
                   .eq(Goods::getId, goodsId)
                   .set(Goods::getGoodsCount, goods.getGoodsCount() - 1)
                   .update();
       } finally {
           // 当前线程为阻塞状态则释放锁
           if (lock.isHeldByCurrentThread()){
               lock.unlock();
           }
       }
       log.info("当前[{}]剩余[{}]台" , goods.getGoodsName(), (goods.getGoodsCount() - 1));
       // transactionId 移除
       MDC.remove("transactionId");
       return ApiResult.success("当前["+ goods.getGoodsName() +"]剩余(台):"+ (goods.getGoodsCount() - 1));
    }
}
